#include <stdio.h>
#include <stdlib.h>

#include <nxos.h>

NX_PRIVATE const char *SyscallTable[] = 
{
    "SysInvalidCall",         /* 0 */
    "SysDebugLog",            /* 1 */
    "SysProcessExit",
    "SysProcessLaunch",
    "SysFileSystemMount",
    "SysFileSystemUnmount",   /* 5 */
    "SysFileSystemSync",
    "SysFileOpen",
    "SysInvalidCall",         /* !removed */
    "SysFileRead",
    "SysFileWrite",           /* 10 */
    "SysFileSetPointer",
    "SysFileSync",
    "SysFileSetMode",
    "SysFileGetStat",
    "SysDirOpen",             /* 15 */
    "SysInvalidCall",         /* !removed */
    "SysDirRead",
    "SysDirResetPointer",
    "SysDirCreate",
    "SysDirDelete",           /* 20 */
    "SysFileRename",
    "SysFileDelete",
    "SysFileAccess",
    "SysFileSetModeToPath",
    "SysFileGetStatFromPath", /* 25 */
    "SysHubRegister",
    "SysHubUnregister",
    "SysInvalidCall",         /* !removed */
    "SysHubCallParamName",
    "SysHubReturn",           /* 30 */
    "SysHubWait",
    "SysHubTranslate",
    "SysFileIoctl",
    "SysMemMap",
    "SysMemUnmap",            /* 35 */
    "SysMemHeap",
    "SysProcessGetCwd",
    "SysProcessSetCwd",
    "SysSoltClose",
    "SysSnapshotCreate",      /* 40 */
    "SysSnapshotFirst",
    "SysSnapshotNext",
    "SysThreadSleep",
    "SysClockGetMillisecond",
    "SysTimeSet",             /* 45 */
    "SysTimeGet",
    "SysThreadCreate",
    "SysThreadExit",
    "SysThreadSuspend",
    "SysThreadResume",        /* 50 */
    "SysThreadWait",
    "SysThreadTerminate",
    "SysThreadGetId",
    "SysThreadGetCurrentId",
    "SysThreadGetCurrent",    /* 55 */
    "SysThreadGetProcessId",
    "SysSemaphoreCreate",
    "SysSemaphoreDestroy",
    "SysSemaphoreWait",
    "SysSemaphoreTryWait",    /* 60 */
    "SysSemaphoreSignal",
    "SysSemaphoreSignalAll",
    "SysSemaphoreGetValue",
    "SysMutexCreate",
    "SysMutexDestroy",        /* 65 */
    "SysMutexAcquire",
    "SysMutexTryAcquire",
    "SysMutexRelease",
    "SysMutexAcquirable",
    "SysDeviceOpen",          /* 70 */
    "SysDeviceRead",
    "SysDeviceWrite",
    "SysDeviceControl",
    "SysSignalSend",
    "SysSignalGetAttr",       /* 75 */
    "SysSignalSetAttr",
    "SysSignalContorl",
    "SysTimerCreate",
    "SysTimerStart",
    "SysTimerStop",           /* 80 */
    "SysDeviceMap",
    "SysDriverRegister",
    "SysThreadYield",
    "SysPollWait",
    "SysShareMemOpen",        /* 85 */
    "SysShareMemMap",
    "SysIpcBind",
    "SysIpcConnect",
    "SysIpcCall",             
    "SysIpcReturn",           /* 90 */
    "SysFifoCreate",
    "SysFifoRead",
    "SysFifoWrite",
    "SysFifoLength",          
    "SysSoltCopy",            /* 95 */
    "SysMemMap2",
    "SysConditionCreate",
    "SysConditionWait",
    "SysConditionWaitTimeout",
    "SysConditionSignal",     /* 100 */
    "SysConditionBroadcast",
    "SysProcessTrace",
    "SysDeviceProbe",
    "SysDeviceEnum",
    "SysMsgQueueCreate",      /* 105 */
    "SysMsgQueueSend",
    "SysMsgQueueTrySend",
    "SysMsgQueueRecv",
    "SysMsgQueueTryRecv",
    "SysMsgQueueFull",        /* 110 */
    "SysMsgQueueEmpty",
    "SysMsgQueueLenght",
    "SysSignalThread",
    "SysPipeCreate",
    "SysPipeConnectByName",   /* 115 */
    "SysPipeDisconnect",
    "SysPipeSend",
    "SysPipeRecv",
    "SysSoltCopyTo",
    "SysSoltInstall",         /* 120 */
    "SysSoltUninstall",
    "SysSoltObject",
};

NX_PRIVATE const char *GetSysName(NX_U32 idx)
{
    if (idx > NX_ARRAY_SIZE(SyscallTable)) {
        return "Unknown";
    }
    return SyscallTable[idx];
}

NX_PRIVATE int HandleSyscallRecord(NX_EventRecord *record, NX_U32 pid)
{
    NX_UArch *regs;
    regs = (NX_UArch *)record->data;
    if (pid != record->header.task)
    {
        // NX_Printf("header: id=%x, ver=%x, task:%d, kw:%d, time:%d\n", 
        //     record->header.id, record->header.version, record->header.task, record->header.keyword, record->header.time);
        // NX_Printf("size %d, data: %s\n", record->size, record->data);
        return 0;
    }

    if (record->header.keyword == 1)
    {
        NX_Printf("Task %d SyscallEnter[%d/%s]: args: %x, %x, %x, %x, %x, %x, %x\n", 
            record->header.task, regs[0], GetSysName(regs[0]), regs[1], regs[2], regs[3], regs[4], regs[5], regs[6], regs[7]);
        if (regs[0] == 2) // exit
        {
            NX_Printf("find child exit!\n");
            return -1;
        }
    }
    else if (record->header.keyword == 2)
    {
        NX_Printf("Task %d SyscallExit[%d/%s]: return: %x\n", record->header.task, regs[0], GetSysName(regs[0]), regs[1]);
    }
    return 0;
}

NX_PRIVATE NX_Error ExecuteCmd(char *cmdline, char *envline, NX_Solt *outChild)
{
    NX_Error err = NX_EOK;
    char appPath[NX_FILE_MAX_NAME] = {0};
    char *spacePos;
    char *cmd = cmdline;
    NX_U32 retCode = 0;
    NX_Solt child;

    while (*cmd == ' ') /* skip space in the head */
    {
        cmd++;
    }
    
    /* get app name */
    spacePos = NX_StrChr(cmd, ' ');
    if (spacePos == NX_NULL) /* no space, only app name */
    {
        NX_StrCopy(appPath, cmd);
    }
    else
    {
        NX_StrCopyN(appPath, cmd, spacePos - cmd + 1);
    }
#ifdef DEBUG
    NX_Printf("strace: app name: `%s`\n", appPath);
#endif

    child = NX_ProcessLaunch(appPath, NX_THREAD_CREATE_SUSPEND, &retCode, cmd, envline);
    err = NX_ErrorGet();
    if (err != NX_EOK)
    {
        NX_Printf("strace: launch app `%s` failed with err %d/%s\n", appPath, err, NX_ErrorToString(err));
        return err;
    }

    *outChild = child;

#ifdef DEBUG
    NX_Printf("strace: app `%s` return code %d\n", appPath, retCode);
#endif
    return err;
}

NX_Solt tracer; 

NX_U32 TraceThread(void *arg)
{
    NX_U32 pid;
    NX_Size count;
    NX_EventRecord records[NX_EVENT_RECORD_MAX_NR];
    NX_Error err;

    pid = (NX_U32)arg;
    
    while (1)
    {
        count = 0;
        NX_MemZero(records, sizeof(records));

        count = NX_DeviceRead(tracer, records, 0, NX_EVENT_RECORD_MAX_NR * sizeof(NX_EventRecord));
        err = NX_ErrorGet();
        if (err != NX_EOK && err != NX_EAGAIN) // ok or skip
        {
            break;
        }

        int i;
        for (i = 0; i < count / sizeof(NX_EventRecord); i++)
        {
            if (HandleSyscallRecord(&records[i], pid))
            {
                goto exit;
            }
        }

        NX_ThreadSleep(100);
    }
exit:
    NX_Printf("trace exit\n");

    return 0;
}

NX_Error NX_Main(char *cmdline, char *envline)
{
    char *argv[32] = {0};
    char newCmdline[128] = {0};
    NX_Solt child = NX_SOLT_INVALID_VALUE;
    NX_Solt tracerPid = NX_SOLT_INVALID_VALUE;
    NX_U32 pid;
    
    int argc = NX_CmdToArray(cmdline, argv, 32);

    if (argc <= 1)
    {
        NX_Printf("strace: invalid args\n");
        return NX_ERROR;
    }

    argc = NX_CmdToBuf(newCmdline, 128, &argv[1]);

    NX_Printf("strace: cmd %s\n", newCmdline);

    if (ExecuteCmd(newCmdline, envline, &child) != NX_EOK)
    {
        NX_Printf("strace: exec %s failed!\n", newCmdline);
        return NX_ERROR;
    }
    // attach event
    tracer = NX_DeviceOpen("e.Syscall", 0);
    if (tracer == NX_SOLT_INVALID_VALUE)
    {
        NX_Printf("strace: attach Syscall event failed!\n");
        return NX_EFAULT;
    }

    pid = NX_ThreadGetProcessId(child);

    NX_Printf("strace: trace process %d start.\n", pid);
    // set pid fileter
    
    NX_TraceFilterBuf buf;
    buf.filter = NX_TRACE_FILTER_TASK;
    buf.value = pid;
    NX_DeviceControl(tracer, NX_TRACE_SET_FILTER, &buf);

    tracerPid = NX_ThreadCreate(NX_NULL, TraceThread, (void *)pid, NX_THREAD_CREATE_NORMAL);
    
    NX_ThreadResume(child);
    NX_ThreadWait(child, NX_NULL);

    NX_ThreadWait(tracerPid, NX_NULL);

    NX_DeviceControl(tracer, NX_TRACE_CLEAR_FILTER, &buf);
    NX_DeviceControl(tracer, NX_TRACE_RESET_FILTER, NX_NULL);

    // deattach event
    NX_DeviceClose(tracer);

    NX_Printf("strace: trace process %d finish.\n", pid);

    return NX_EOK;
}
